#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

using namespace std;

const int gmax_cap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(const int &max_cap = gmax_cap)
        : _max_cap(gmax_cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }

    void push(const T &in) // 输入型参数 -- const &
    {
        // 进来先加锁，因为要保证同一时刻只有一个线程在往队列中读或写
        pthread_mutex_lock(&_mutex);

        // 1.判断是否满了
        // 细节: 充当条件判断的语法必须是while，不能用if

        // 在一个线程调用 pthread_cond_wait 后，它通常期望在符合某个特定条件——例如，队列不再满，之后才会被唤醒。
        // 然而，线程有时会在没有这个条件被满足的情况下被唤醒。这就是假唤醒。
        // 当线程发生假唤醒时，它会试图获取互斥锁来继续执行后续代码。

        // 如果互斥锁可用，即没有其他线程正在占用锁，那么这个发生假唤醒的线程确实可以立即获取这个锁。
        // 但即使它获取了锁，这并不意味着它可以安全地继续其后续的操作，因为唤醒它的条件实际上可能并未成立。
        // 如果线程因假唤醒而继续执行，且没有适当的再次检查条件的机制，它可能会在不适当的时候操作共享资源，导致逻辑上的错误或数据竞争。

        // 这就是在等待条件变量时使用 while 循环来重新检查条件的重要性。
        // 当使用 while 循环时，即便是在发生假唤醒，线程被唤醒并获取锁之后，它仍会检查条件是否真的满足了。
        // 如果不满足，线程会释放锁并重新等待，这样可以确保线程执行后续操作的前提是条件确实已经得到满足。

        // if (is_full())  // bug
        while (is_full())
        {
            pthread_cond_wait(&_pcond, &_mutex); // 如果队列满了，就进入pcond条件变量中等待
        }

        // 2.队列一定没满
        _q.push(in);

        // 3.此时队列一定有数据，所以唤醒(通知) 排在前面的消费者可以来消费了
        pthread_cond_signal(&_ccond);

        pthread_mutex_unlock(&_mutex);
    }

    void pop(T *out) // 输出型参数 -- *     输入输出型参数 -- &
    {
        // 进来先加锁，因为要保证同一时刻只有一个线程在往队列中读或写
        pthread_mutex_lock(&_mutex);

        // 1.判断
        // if (is_empty())  // bug
        while (is_empty())
        {
            pthread_cond_wait(&_ccond, &_mutex); // 如果队列没数据，就进入ccond条件变量中等待
        }

        // 2.队列一定有数据
        *out = _q.front();
        _q.pop();

        // 3.此时队列一定有空位置，所以唤醒(通知) 排在前面的生产者可以来生产了

        pthread_cond_signal(&_pcond);

        pthread_mutex_unlock(&_mutex);

        // pthread_cond_signal(&_pcond)也可以放在临界区外面，不过推荐放在里面
        // 如果是放在外面，因为是我先释放锁，所以一释放就有可能被其他线程拿走，但是不影响，拿走的时候都是在pthread_cond_wait()中继续运行
        // 但是由于我们已经改成while，他们能否继续执行还需while条件满足，所以也不用担心
    }

private:
    bool is_full()
    {
        return _q.size() == _max_cap;
    }

    bool is_empty()
    {
        return _q.empty();
    }

private:
    queue<T> _q;
    int _max_cap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond; // 将来多生产者的条件变量(我们可以通俗地理解为"等待队列")
    pthread_cond_t _ccond; // 将来多消费者的条件变量(我们可以通俗地理解为"等待队列")
};